"""

.. module:: <module>
.. moduleauthor:: @author: 

The purpose of this module is ... 
   
.. seealso:: modules :mod:`<module>`

*Constants*:
.. data:: constant_1
        
    constant_1 is used for ......
        
.. data:: constant_2
        
    constant_2 is used for ......
"""

# Imports
#
from Queue import Queue
from threading import Thread
import types
import sys
import logging

from runtime.spaces import SpacesContext
from runtime.boot import LoggingLevels

# Constants
#

# Classes
#


class NonTaskException(Exception):
    """This class represents an exception for a bad task class definition.

    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: constant_1
        
            constant_1 is used for ......
        
        .. data:: constant_2
        
            constant_2 is used for ......
    """
    
    # Class constants
    #
    
    pass

class TaskException(Exception):
    """This class represents an exception for ...

    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: constant_1
        
            constant_1 is used for ......
        
        .. data:: constant_2
        
            constant_2 is used for ......
    """
    
    pass

class Task(Thread):
    """This class represents a task that is a thread with its own SpacesContext.
    Each sub-class of task defines a set of callback methods that watch or 
    process on specific methods (i.e. 'get', 'find', 'delete', 'put') called on 
    a particular URI in Spaces. 
    
    A Task callback method has a defined signature which consists of the following
    parameters:
        method - the method invoked on the uri (i.e. 'get', 'find', 'delete', 'put') 
        uri - the uri invoked on Spaces
        return_value - the value returned by Spaces. This can be a datastore.resource.Resource or datastore.uri.Uri
        result - None or an exception class.

    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: constant_1
        
            constant_1 is used for ......
        
        .. data:: constant_2
        
            constant_2 is used for ......
    """
    
    # Class constants
    #
    
    def __init__(self, ds, spaces, task_uri, task_def_rs):
        # <purpose>
        #
        # Parameters:
        #     ds : datastore.database.DataStore
        #     spaces : runtime.spaces.Spaces - Reference to spaces
        #     task_uri : String - the uri for this task. The root uri will most
        #                likely be the 'binding' uri for this task.
        #     task_def_rs : datastore.resource.Resource - Task definition resource.
        #
        
        Thread.__init__(self)
        
        self.logger = logging.getLogger('yarra.runtime.task.Task')
        self.name = self.__class__.__name__
        #
        self.__stop = False                         # Callback's are invoked until set to True
        self.ds = ds
        self.spaces = spaces
        self.queue = Queue()
        self.spaces_context = SpacesContext(spaces, self.queue)    # Callback's may access our default context to access spaces.    
        self.task_uri = task_uri
        #   
        # These watches allow a put to initialise this task and a delete to shut it down nicely.
        #
        watch_key_for_start = self.spaces_context.watch(['put'], task_uri, 'cb_start')
        self.spaces_context.watch(['delete'], task_uri, 'cb_stop')
        # Create a status resource and invoke cb_start.
        self.__task_def_rs = task_def_rs
        self.__task_def_rs.state = self.spaces_context.uri('runtime:').task.state.starting
        self.spaces_context.put(task_uri, self.__task_def_rs)   # Invoke cb_start
        self.__task_def_rs.state = self.spaces_context.uri('runtime:').task.state.running
        # Further puts will invoke the update method.
        self.spaces_context.delete_watch(watch_key_for_start)
        self.spaces_context.put(task_uri, self.__task_def_rs)   # Update our state (but don't fire cb_update)
        self.spaces_context.watch(['put'], task_uri, 'cb_update')
      
    def run(self):
        """This function loops, processing callback's until stopped.
        """
        
        # message handling loop for the task. Wait until the next callback to process.
        self.logger.log(LoggingLevels.TRACE, "Started")
        
        while not self.__stop:
            (callback, method, uri, return_value, result, spaces_context, kwargs) = self.queue.get(True) 
            
            self.logger.log(LoggingLevels.TRACE, "***** Queue.get() returns: callback (%s), method (%s), uri (%s), return_value (%s), result (%s)" % (callback, method, uri, return_value, result))
            
            if callback is None:
                self.logger.log(logging.ERROR, "Callback (%s) method is None. Callback not processed." % callback)
                continue
            
            self.logger.log(LoggingLevels.TRACE, "callback (%s), method (%s), uri (%s), return_value (%s), result (%s)" % (callback, method, uri, return_value, result))
            
            if type(callback) == types.StringType:
                # cb_stop is used to signal that the process is finished and is the last callback to be processed.                
                if callback is 'cb_stop':
                    self.__stop = True
                # get a reference to the callback method
                callback_method = getattr(self, callback)
            else:
                callback_method = callback
            # invoke the callback
            if (spaces_context is None):
                spaces_context = self.spaces_context
            # Within a spaces context, for a process handler or subsequent callback, someone must
            # set do_return to True. If the callback returns a value, then this is past as the second
            # element of the Tuple. 
            ret = None
            try:
                ret = callback_method(method, uri, return_value, result, spaces_context, **kwargs)
            except Exception, e:
                exc_info = sys.exc_info()
                # adding {'exc_info': 1} as a kwarg makes the logger print out the traceback (stack trace)
                
                self.logger.exception("Callback (%s) error. Exception: %s. Value: %s." % (callback, exc_info[0], exc_info[1]))
                
                spaces_context.return_from_callback(spaces_context, exception=e)                
            # if a callback return id is specified, this means we should return the value back to spaces
            if (ret is None):
                self.logger.log(LoggingLevels.TRACE, "return=None")
            else:
                (do_return, callback_return_value) = ret
                
                self.logger.log(LoggingLevels.TRACE, "return=%s" % callback_return_value)
                
                if do_return:
                    spaces_context.return_from_callback(spaces_context, callback_return_value)
        
        self.logger.log(LoggingLevels.TRACE, "Stopped")

    def task_start(self):
        """This function starts the thread and waits until the start message is 
        processed. This supports testing. 
        """
        
        self.start()							# start the thread
  
    def task_stop(self):
        """This function signals the task to stop and invoke the cb_stop() callback.
        """
        
        self.logger.log(LoggingLevels.TRACE, "%s" % self.task_uri)

        self.spaces_context.delete(self.task_uri)

    def cb_start(self, method, uri, new_uri, result, context, **kwargs):
        """This function is the default implementation of the start method - the
        first callback invoked for the task. This is normally overridden.
        
        :param method: string - the method name that was called. (i.e. 'get', 
        'put', 'delete', 'find')
        :param uri: Uri - the uri from the caller request.
        :param new_uri: Uri
        :param result: <class> or <type>
        :param context: runtime.spaces.SpaceContext
        :param kwargs: Dictionary - The kwargs supplied when the caller 
        called a method on spaces.
        :rtype: <return type>
        """
        
        self.logger.log(logging.DEBUG, "")
        return (False, None)

    def cb_update(self, method, uri, new_uri, result, context, **kwargs):
        """This function is the default implementation of the update method - 
        called whenever the task resource is updated by a put. This is 
        overridden if external updates are expected.
        
        :param method: string - the method name that was called. (i.e. 'get', 
        'put', 'delete', 'find')
        :param uri: Uri - the uri from the caller request.
        :param new_uri: Uri
        :param result: <class> or <type>
        :param context: runtime.spaces.SpaceContext
        :param kwargs: Dictionary - The kwargs supplied when the caller 
        called a method on spaces.
        :rtype: <return type>
        """
        
        self.logger.log(logging.DEBUG, "")
        return (False, None)

    def cb_stop(self, method, uri, deleted_res, result, context, **kwargs):
        """This function is the default implementation of the stop method - the
        last callback invoked for this task after stop_task() is called.
        This should be overridden if cleanup is needed.
        
        Note: clean-up cannot access any spaces function that returns a value 
        or otherwise access the queue because the callback loop has already 
        terminated.
        
        :param method: string - the method name that was called. (i.e. 'get', 
        'put', 'delete', 'find')
        :param uri: Uri - the uri from the caller request.
        :param deleted_res: datastore.resource.Resource
        :param result: <class> or <type>
        :param context: runtime.spaces.SpaceContext
        :param kwargs: Dictionary - The kwargs supplied when the caller 
        called a method on spaces.
        :rtype: <return type>
        """
        
        self.logger.log(logging.DEBUG, "")
        return (False, None)

    def cb_stop_test(self, method, uri, res, result, context, **kwargs):
        """This function is for testing only.
        
        :param method: string - the method name that was called. (i.e. 'get', 
        'put', 'delete', 'find')
        :param uri: Uri - the uri from the caller request.
        :param res: datastore.resource.Resource
        :param result: <class> or <type>
        :param context: runtime.spaces.SpaceContext
        :param kwargs: Dictionary - The kwargs supplied when the caller 
        called a method on spaces.
        :rtype: <return type>
        """
        
        self.logger.log(LoggingLevels.TRACE, "")
        self.__stop = True
        return (False, None)